/*
 fbv  --  simple image viewer for the linux framebuffer
 Copyright (C) 2002  Tomasz Sterna

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

#include "fbv.h"

#define BMP_TORASTER_OFFSET	10
#define BMP_SIZE_OFFSET		18
#define BMP_BPP_OFFSET		28
#define BMP_RLE_OFFSET		30
#define BMP_COLOR_OFFSET	54

#define fill4B(a)	( ( 4 - ( (a) % 4 ) ) & 0x03)

struct color {
	unsigned char red;
	unsigned char green;
	unsigned char blue;
};

int fh_bmp_id(char *name) {
	printf("function : %s\n", __func__);
	printf("\tparalist : name=%s\n", name);

	int fd;
	char id[2];

	fd = open(name, O_RDONLY);
	if (fd == -1) {
		return (0);
	}

	read(fd, id, 2);
	close(fd);
	if (id[0] == 'B' && id[1] == 'M') {
		return (1);
	}
	return (0);
}

// 获取调色板信息
void fetch_pallete(int fd, struct color pallete[], int count) {
	unsigned char buff[4];
	int i;

	int readbytes = count << 2;
	char *buf = (char *) malloc(sizeof(char) * readbytes);
	lseek(fd, BMP_COLOR_OFFSET, SEEK_SET);

	read(fd, buff, readbytes);
	for (i = 0; i < readbytes; i += 4) {
		pallete[i].red = buff[i + 2];
		pallete[i].green = buff[i + 1];
		pallete[i].blue = buff[i];
	}

	free(buf);
	return;
}

int fh_bmp_load(char *name, unsigned char *buffer, unsigned char **alpha, int x,
		int y) {
	printf("function : %s\n", __func__);
	printf("\tparalist : name=%s, x = %d, y = %d\n", name, x, y);

	int fd, bpp, raster, i, j, k, skip;
	unsigned char buff[4];
	unsigned char *wr_buffer = buffer + x * (y - 1) * 3;
	struct color pallete[256];

	fd = open(name, O_RDONLY);
	if (fd == -1) {
		return (FH_ERROR_FILE);
	}

	//  get bmp data start position
	if (lseek(fd, BMP_TORASTER_OFFSET, SEEK_SET) == -1) {
		return (FH_ERROR_FORMAT);
	}
	read(fd, buff, 4);
	raster = buff[0] + (buff[1] << 8) + (buff[2] << 16) + (buff[3] << 24);

	// get bmp bit per pixel
	if (lseek(fd, BMP_BPP_OFFSET, SEEK_SET) == -1) {
		return (FH_ERROR_FORMAT);
	}
	read(fd, buff, 2);
	bpp = buff[0] + (buff[1] << 8);

	switch (bpp) {
	case 1: {/* monochrome */
		int linebytes = x >> 3;
		char *buf = (char *) malloc(sizeof(char) * linebytes);
		skip = fill4B(linebytes+(x%8?1:0));
		lseek(fd, raster, SEEK_SET);

		for (i = 0; i < y; i++) {
			read(fd, buf, linebytes);
			for (j = 0; j < linebytes; j++) {
				for (k = 0; k < 8; k++) {
					if (*(buf + j) & 0x80) {
						*wr_buffer++ = 0xff;
						*wr_buffer++ = 0xff;
						*wr_buffer++ = 0xff;
					} else {
						*wr_buffer++ = 0x00;
						*wr_buffer++ = 0x00;
						*wr_buffer++ = 0x00;
					}
					*(buf + j) = *(buf + j) << 1;
				}

			}
			if (x % 8) {
				for (k = 0; k < x % 8; k++) {
					if (*(buf + j) & 0x80) {
						*wr_buffer++ = 0xff;
						*wr_buffer++ = 0xff;
						*wr_buffer++ = 0xff;
					} else {
						*wr_buffer++ = 0x00;
						*wr_buffer++ = 0x00;
						*wr_buffer++ = 0x00;
					}
					*(buf + j) = *(buf + j) << 1;
				}

			}
			if (skip)
				read(fd, buf, skip);

			wr_buffer -= x * 6; /* backoff 2 lines - x*2 *3 */
		}
		free(buf);
		break;
	}
	case 4: {/* 4bit palletized */
		int linebytes = x >> 1;
		char *buf = (char *) malloc(sizeof(char) * linebytes);
		char color1, color2;
		skip = fill4B(linebytes+x%2);
		fetch_pallete(fd, pallete, 16);
		lseek(fd, raster, SEEK_SET);

		for (i = 0; i < y; i++) {
			read(fd, buf, linebytes);
			for (j = 0; j < linebytes; j++) {
				color1 = *(buf + j) >> 4;
				color2 = *(buf + j) & 0x0f;
				*wr_buffer++ = pallete[color1].red;
				*wr_buffer++ = pallete[color1].green;
				*wr_buffer++ = pallete[color1].blue;
				*wr_buffer++ = pallete[color2].red;
				*wr_buffer++ = pallete[color2].green;
				*wr_buffer++ = pallete[color2].blue;
			}
			if (x % 2) {
				color1 = *(buf + j) >> 4;
				*wr_buffer++ = pallete[color1].red;
				*wr_buffer++ = pallete[color1].green;
				*wr_buffer++ = pallete[color1].blue;
			}
			if (skip)
				read(fd, buf, skip);

			wr_buffer -= x * 6; /* backoff 2 lines - x*2 *3 */
		}
		free(buf);
		break;
	}
	case 8: {/* 8bit palletized */
		char *buf = (char *) malloc(sizeof(char) * x);

		skip = fill4B(x);
		fetch_pallete(fd, pallete, 256);
		lseek(fd, raster, SEEK_SET);

		for (i = 0; i < y; i++) {
			read(fd, buf, x);
			for (j = 0; j < x; j++) {
				*wr_buffer++ = pallete[*(buf + j)].red;
				*wr_buffer++ = pallete[*(buf + j)].green;
				*wr_buffer++ = pallete[*(buf + j)].blue;
			}
			if (skip)
				read(fd, buf, skip);

			wr_buffer -= x * 6; /* backoff 2 lines - x*2 *3 */
		}
		free(buf);
		break;
	}
	case 16: {/* 16bit RGB */
		return (FH_ERROR_FORMAT);
		break;
	}
	case 24: {/* 24bit RGB */
		int linebytes = x * 3;
		char *buf = (char *) malloc(sizeof(char) * linebytes);

		skip = fill4B(linebytes);
		lseek(fd, raster, SEEK_SET);

		for (i = 0; i < y; i++) {
			read(fd, buf, linebytes);
			for (j = 0, k = 0; j < x; j++, k += 3) {
				*wr_buffer++ = *(buf + k + 2); // R
				*wr_buffer++ = *(buf + k + 1); // G
				*wr_buffer++ = *(buf + k); // B
			}
			if (skip)
				read(fd, buf, skip);

			wr_buffer -= (linebytes << 1); /* backoff 2 lines - x*2 *3 */
		}
		free(buf);
		break;
	}
	default:
		return (FH_ERROR_FORMAT);
	}

	close(fd);
	return (FH_ERROR_OK);
}
int fh_bmp_getsize(char *name, int *x, int *y) {
	int fd;
	unsigned char size[4];

	fd = open(name, O_RDONLY);
	if (fd == -1) {
		return (FH_ERROR_FILE);
	}
	// bmp格式规定在18字节那个地方开始用32位整型存储图像的宽度跟高度
	if (lseek(fd, BMP_SIZE_OFFSET, SEEK_SET) == -1) {
		return (FH_ERROR_FORMAT);
	}

	read(fd, size, 4);
	// width pixel, 4 bytes, little endian
	*x = size[0] + (size[1] << 8) + (size[2] << 16) + (size[3] << 24);
//	*x-=1;
	read(fd, size, 4);
	*y = size[0] + (size[1] << 8) + (size[2] << 16) + (size[3] << 24);

	close(fd);
	return (FH_ERROR_OK);
}
